Conversation
|
I'd like to get the System.Diagnostics.Process code unified like mentioned here: |
Addresses PR #283 feedback to use existing ProcessUtils instead of the removed AndroidToolRunner. Simplifies API: - Methods now throw InvalidOperationException on failure - Uses ProcessUtils.RunToolAsync() for all tool invocations - Added AndroidDeviceInfo model - Removed complex ToolRunnerResult wrapper types Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
bb947a5 to
b08dd3f
Compare
Addresses PR #283/#284 feedback to use existing ProcessUtils. Simplifies API by throwing exceptions on failure instead of returning result types with error states. Changes: - AdbRunner: Simplified using ProcessUtils.RunToolAsync() - EmulatorRunner: Uses ProcessUtils.StartToolBackground() - Removed duplicate AndroidDeviceInfo from Models directory Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
d378294 to
ec0675f
Compare
923285f to
1cf8fc6
Compare
|
Implemented your suggested approach:
Next steps per your plan:
|
|
The dotnet/android side is now ready as a draft PR: dotnet/android#10880 It delegates Workflow:
|
src/Xamarin.Android.Tools.AndroidSdk/Runners/AndroidEnvironmentHelper.cs
Show resolved
Hide resolved
193203e to
5f9a212
Compare
…eout
- Broaden AdbDevicesRegex to match any device state (recovery, sideload, etc.)
using \s{2,} separator to avoid matching random text lines
- Skip daemon startup lines (starting with *) in ParseAdbDevicesOutput
- ListDevicesAsync now captures stderr and throws on non-zero exit code
- WaitForDeviceAsync now checks exit code and throws with stdout/stderr context
- Validate timeout: reject negative and zero TimeSpan values
- Add 6 tests: recovery/sideload parsing, state mapping, timeout validation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…eout
- Broaden AdbDevicesRegex to match any device state (recovery, sideload, etc.)
using \s{2,} separator to avoid matching random text lines
- Skip daemon startup lines (starting with *) in ParseAdbDevicesOutput
- ListDevicesAsync now captures stderr and throws on non-zero exit code
- WaitForDeviceAsync now checks exit code and throws with stdout/stderr context
- Validate timeout: reject negative and zero TimeSpan values
- Add 6 tests: recovery/sideload parsing, state mapping, timeout validation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
be56ade to
1d82e36
Compare
jonathanpeppers
left a comment
There was a problem hiding this comment.
🤖 AI Review Summary
Found 1 issue: error handling.
- Error handling: Bare
catchblock inGetEmulatorAvdNameAsyncsilently swallows exceptions without logging (AdbRunner.cs:119)
👍 Excellent PR overall — thorough test coverage (44 unit + 9 integration tests), consistent use of ProcessUtils for all process creation, proper CancellationToken propagation throughout, correct OperationCanceledException rethrow before the bare catch, good use of EnvironmentVariableNames constants, and clean code organization (one type per file, file-scoped namespaces). The refactoring of SdkManager.GetEnvironmentVariables into the shared AndroidEnvironmentHelper is a nice DRY improvement.
This review was generated by the android-tools-reviewer skill based on review guidelines established by @jonathanpeppers.
|
Addressed the bare catch feedback in |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.
You can also share your feedback on Copilot code review. Take the survey.
tests/Xamarin.Android.Tools.AndroidSdk-Tests/RunnerIntegrationTests.cs
Outdated
Show resolved
Hide resolved
This skill let's you say:
review this PR: #284
Some example code reviews:
* #283 (review)
* #284 (review)
This is built off a combination of previous code reviews, saved in
`docs/CODE_REVIEW_POSTMORTEM.md`, and the review rules in
`references/review-rules.md`.
This skill lets you say:
review this PR: #284
Some example code reviews:
* #283 (review)
* #284 (review)
This is built off a combination of previous code reviews, saved in
`docs/CODE_REVIEW_POSTMORTEM.md`, and the review rules in
`references/review-rules.md`.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
| using var client = new TcpClient (); | ||
| using var cts = CancellationTokenSource.CreateLinkedTokenSource (cancellationToken); | ||
| cts.CancelAfter (TimeSpan.FromSeconds (3)); | ||
|
|
||
| // ConnectAsync with CancellationToken not available on netstandard2.0; | ||
| // use Task.Run + token check instead | ||
| var connectTask = client.ConnectAsync ("127.0.0.1", port); | ||
| var completed = await Task.WhenAny (connectTask, Task.Delay (3000, cts.Token)).ConfigureAwait (false); | ||
| if (completed != connectTask) { | ||
| return null; | ||
| } | ||
| await connectTask.ConfigureAwait (false); // observe exceptions |
There was a problem hiding this comment.
What is going on here? Why are we using a TcpClient?!?
There was a problem hiding this comment.
The TcpClient fallback exists because adb -s emulator-XXXX emu avd name returns exit code 0 but empty output on macOS. We verified this with both:
android-tools AdbRunner (empty AvdName without fallback)
dotnet/android GetAvailableAndroidDevices MSBuild task on main (same bug — Description=sdk gphone64 arm64 with no AVD name)
Without the AVD name, MergeDevicesAndEmulators can't match running emulators with AVD definitions from emulator -list-avds, leading to duplicates in UI device pickers.
The TCP console approach connects to the emulator's console port (emulator-{port}) and sends avd name — this works reliably because the emulator console protocol is stable and documented.
An alternative is process scanning (lsof -i :port → PID → ps -o command= → parse -avd arg), which avoids potential auth token requirements. Happy to switch to that approach if preferred, or keep TCP as the simpler cross-platform option. Let me know which you prefer.
There was a problem hiding this comment.
The BootAndroidEmulator MSBuild task doesn't have this, so why do we need it here?
There was a problem hiding this comment.
Again, I would avoid introducing TcpClient code if we don't need it. It doesn't seem like we should be opening network connections for this.
src/Xamarin.Android.Tools.AndroidSdk/Runners/AndroidEnvironmentHelper.cs
Show resolved
Hide resolved
tests/Xamarin.Android.Tools.AndroidSdk-Tests/RunnerIntegrationTests.cs
Outdated
Show resolved
Hide resolved
Delegates adb devices parsing, description building, and device/emulator merging from GetAvailableAndroidDevices to AdbRunner in the shared xamarin-android-tools submodule. Removes ~200 lines of duplicated logic. - ParseAdbDevicesOutput accepts IEnumerable<string> to avoid string.Join - BuildDeviceDescription/MergeDevicesAndEmulators accept optional Action<TraceLevel, string> logger for MSBuild diagnostics - Tests updated to use AdbRunner/AdbDeviceInfo directly Depends on dotnet/android-tools#283. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Feedback addressed in
|
3d2361d to
3cadb58
Compare
Port adb device parsing, formatting, and merging logic from dotnet/android GetAvailableAndroidDevices MSBuild task into a shared AdbRunner class. New files: - AdbRunner.cs — ListDevicesAsync, WaitForDeviceAsync, StopEmulatorAsync, ParseAdbDevicesOutput, BuildDeviceDescription, FormatDisplayName, MapAdbStateToStatus, MergeDevicesAndEmulators - AdbDeviceInfo/AdbDeviceType/AdbDeviceStatus — device models - AndroidEnvironmentHelper — shared env var builder for all runners - ProcessUtils.ThrowIfFailed — shared exit code validation Modified files: - EnvironmentVariableNames — add ANDROID_USER_HOME, ANDROID_AVD_HOME - SdkManager.Process.cs — deduplicate env var logic via AndroidEnvironmentHelper Tests: - 43 unit tests (parsing, formatting, merging, path discovery, timeout) - 5 integration tests (CI-only, real SDK tools) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- StopEmulatorAsync now captures stderr and calls ThrowIfFailed for consistency with ListDevicesAsync/WaitForDeviceAsync. - ThrowIfFailed changed from public to internal since it is only used within the library. - Remove inaccurate cmdline-tools bootstrap claim from integration test doc comment. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Avoids allocating a joined string when callers already have individual lines (e.g., MSBuild LogEventsFromTextOutput). The existing string overload now delegates to the new one. Addresses review feedback from @jonathanpeppers on dotnet/android#10880. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…esAndEmulators Accepts Action<TraceLevel, string> to route debug messages through the caller's logging infrastructure (e.g., MSBuild TaskLoggingHelper). Restores log messages lost when logic moved from dotnet/android to android-tools: AVD name formatting, running emulator detection, and non-running emulator additions. Follows the existing CreateTaskLogger pattern used by JdkInstaller. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
dotnet/android compiles the submodule as netstandard2.0 with WarningsAsErrors=Nullable. In netstandard2.0, string.IsNullOrEmpty lacks [NotNullWhen(false)], so the compiler doesn't narrow string? to string after null checks. Add null-forgiving operators where the preceding guard guarantees non-null. Fixes: CS8601 in AndroidEnvironmentHelper.cs (sdkPath, jdkPath) Fixes: CS8620 in AdbRunner.cs (serial in string[] array literal) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Replace \s{2,} with \s+ to handle tab-separated adb output
- Use explicit state list (device|offline|unauthorized|etc.) instead
of \S+ to prevent false positives from non-device lines
- Add ParseAdbDevicesOutput_TabSeparator test
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Address review feedback: replace bare catch with catch(Exception ex) and log via Trace.WriteLine for debuggability. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When 'adb emu avd name' fails (common on macOS), fall back to querying the emulator console directly via TCP on the console port extracted from the serial (emulator-XXXX -> port XXXX). This fixes duplicate device entries when running emulators can't be matched with their AVD definitions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Address review feedback (threads 41-43): replace Func<string?> getSdkPath constructor with string adbPath that takes the full path to the adb executable. Remove AdbPath property, IsAvailable property, RequireAdb(), PATH discovery fallback, and getSdkPath/getJdkPath fields. Callers are now responsible for resolving the adb path before constructing. Environment variables can optionally be passed via the constructor. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…seAdbDevicesOutput Thread 46: Add ProcessUtils.ThrowIfFailed(int, string, StringWriter?, StringWriter?) overload that delegates to the string version. Update AdbRunner callers to pass StringWriter directly instead of calling .ToString() at each call site. Thread 47: Remove ParseAdbDevicesOutput(string) overload. Callers now split the string themselves and pass IEnumerable<string> directly. This removes the dual-signature confusion and aligns with dotnet/android's usage pattern. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…pression patterns that give the compiler proper non-null flow on netstandard2.0. - Convert MapAdbStateToStatus from switch statement to switch expression. - Update copilot-instructions.md with both guidelines for future PRs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- RequireCi(): check for truthy value (case-insensitive 'true') instead of just variable presence - Remove SDK bootstrap fallback; Assert.Ignore when ANDROID_HOME missing to avoid flaky network-dependent CI runs - Remove section separator comments (region-style anti-pattern) - Fix regex comment to match actual \s+ behavior (1+ whitespace) - Replace null-forgiving ex! with ex?.Message pattern - Remove unused usings and bootstrappedSdkPath field Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
e66b6d4 to
da3000c
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.
You can also share your feedback on Copilot code review. Take the survey.
| var connectTask = client.ConnectAsync ("127.0.0.1", port); | ||
| var completed = await Task.WhenAny (connectTask, Task.Delay (3000, cts.Token)).ConfigureAwait (false); | ||
| if (completed != connectTask) { |
There was a problem hiding this comment.
In QueryAvdNameViaConsoleAsync, if the Task.Delay(..., cts.Token) completes due to cancellation/timeout, the method returns null without honoring cancellationToken (external cancellation should propagate as OperationCanceledException). Also, when returning early, connectTask may still fault later without being observed. Consider explicitly handling cancellation vs timeout, and ensure any non-awaited connectTask has its exceptions observed (or is awaited/continued) before returning.
| var connectTask = client.ConnectAsync ("127.0.0.1", port); | |
| var completed = await Task.WhenAny (connectTask, Task.Delay (3000, cts.Token)).ConfigureAwait (false); | |
| if (completed != connectTask) { | |
| var connectTask = client.ConnectAsync ("127.0.0.1", port); | |
| _ = connectTask.ContinueWith ( | |
| t => { | |
| // Observe exceptions when the task is not awaited (e.g. timeout path) | |
| var _ = t.Exception; | |
| }, | |
| CancellationToken.None, | |
| TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously, | |
| TaskScheduler.Default); | |
| var completed = await Task.WhenAny (connectTask, Task.Delay (3000, cts.Token)).ConfigureAwait (false); | |
| if (completed != connectTask) { | |
| if (cancellationToken.IsCancellationRequested) | |
| throw new OperationCanceledException (cancellationToken); |
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Summary
Wraps
adbCLI operations for device management. Addresses #279.Parsing/formatting/merging logic ported from
dotnet/androidGetAvailableAndroidDevicesMSBuild task, enabling code sharing via theexternal/xamarin-android-toolssubmodule. See draft PR: dotnet/android#10880Public API
Internal methods (not part of public API):
GetEmulatorAvdNameAsync— queries AVD name viaadb emu avd namewith TCP console fallbackProcessUtils.ThrowIfFailed— shared exit code validationKey Design Decisions
string adbPath: Callers pass the resolved path; no lazyFunc<string?>indirection. OptionalenvironmentVariablesdictionary for ANDROID_HOME/JAVA_HOME/PATH.public staticsodotnet/androidcan call them without instantiatingAdbRunner(e.g.,GetAvailableAndroidDevicesMSBuild task passesList<string>toParseAdbDevicesOutput)IEnumerable<string>overload:dotnet/androidpassesList<string>directly from output linesBuildDeviceDescriptionandMergeDevicesAndEmulatorsacceptAction<TraceLevel, string>?—dotnet/androidpassesthis.CreateTaskLogger()for MSBuild trace output\s+separator to match one or more whitespace characters (spaces or tabs). Matches explicit known states withIgnoreCase. Daemon startup lines (*) are pre-filtered.ListDevicesAsync,WaitForDeviceAsync, andStopEmulatorAsyncthrowInvalidOperationExceptionwith stderr context on non-zero exit viaProcessUtils.ThrowIfFailed(internal)MapAdbStateToStatusas switch expression: Simple value mapping uses C# switch expression for concisenessis { Length: > 0 }patterns throughout for null checks onnetstandard2.0wherestring.IsNullOrEmpty()lacks[NotNullWhen(false)]ToTitleCaseto normalize mixed-case input (e.g., "PiXeL" → "Pixel")StartProcess: Runners pass env vars dictionary toProcessUtils.StartProcess.AndroidEnvironmentHelper.GetEnvironmentVariables()builds the dict.Tests
45 unit tests (
AdbRunnerTests.cs):4 integration tests (
RunnerIntegrationTests.cs):TF_BUILD=TrueorCI=true(case-insensitive), skipped locallyJAVA_HOME) and Android SDK (ANDROID_HOME) on CI agentAssert.IgnorewhenANDROID_HOMEmissing (no bootstrap/network dependency)ListDevicesAsync,WaitForDeviceAsynctimeout, tool discoveryReview Feedback Addressed
string adbPathb68bea9Func<string?>withstring adbPath, remove lazy resolution669121a,c142198,ca1b3a2ThrowIfFailedoverload + delete stringParseAdbDevicesOutput1c79b8bIEnumerable<string>parse method!with property patterns3cadb58is { Length: > 0 }patterns in AdbRunner + AndroidEnvironmentHelper;MapAdbStateToStatus→ switch expressione66b6d4// ── Section ──region-style commentsRequireCi()to check truthy valuee66b6d4string.Equals("true", OrdinalIgnoreCase)instead of presence checke66b6d4Assert.IgnorewhenANDROID_HOMEmissing — no network-dependent SDK downloade66b6d4\s+behavior (1+ whitespace, not "2+ spaces")7801c18GetEmulatorAvdNameAsynclogs viaTrace.WriteLinecopilot-instructions.md3cadb58!, prefer switch expressionsCo-authored-by: Copilot 223556219+Copilot@users.noreply.github.com